package com.insight.common.util;

import io.minio.CopyConditions;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import io.minio.Result;
import io.minio.errors.InvalidEndpointException;
import io.minio.errors.InvalidPortException;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;
import com.insight.common.constant.FileStorageTypePrefix;
import com.insight.common.util.filter.StrAttackFilter;
import org.springframework.web.multipart.MultipartFile;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * minio文件上传工具类
 */
@Slf4j
public class MinioUtil {
    private static String minioUrl;
    private static String externalNetworkUrl;
    private static String minioName;
    private static String minioPass;
    private static String bucketName;
    private static int port;
    private static boolean secure = false;

    public static void setMinioUrl(String minioUrl) {
        MinioUtil.minioUrl = minioUrl;
    }

    public static void setMinioName(String minioName) {
        MinioUtil.minioName = minioName;
    }

    public static void setMinioPass(String minioPass) {
        MinioUtil.minioPass = minioPass;
    }

    public static void setBucketName(String bucketName) {
        MinioUtil.bucketName = bucketName;
    }

    public static void setExternalNetworkUrl(String url) {
        if (!url.endsWith("/")) {
            url += "/";
        }
        MinioUtil.externalNetworkUrl = url;
    }

    public static String getMinioUrl() {
        return minioUrl;
    }

    public static String getBucketName() {
        return bucketName;
    }

    public static int getPort() {
        return port;
    }

    public static void setPort(int port) {
        MinioUtil.port = port;
    }

    public static boolean getSecure() {
        return secure;
    }

    public static void setSecure(boolean secure) {
        MinioUtil.secure = secure;
    }

    private static MinioClient minioClient = null;

    /**
     * 上传文件
     * 参数 customBucket 是用户自定义的桶，不使用配置文件中配置的bucket，而是把文件放到用户指定的桶中
     * 目前不打算分桶，而是通过biz参数区分不同路径。全局唯一的桶名为insight，下面的路径分了knowledge、等。需要前端去配合。
     *
     * @param file
     * @return
     */
    static String upload(MultipartFile file, String bizPath, String customBucket) {
        try {
            InputStream stream = file.getInputStream();
            // 获取文件名
            String orgName = file.getOriginalFilename();
            if ("".equals(orgName)) {
                orgName = file.getName();
            }
            return upload(stream, orgName, bizPath, customBucket);
        } catch (IOException e) {
            throw new IllegalArgumentException("文件上传失败：获取输入流异常", e);
        }
    }

    static String upload(InputStream inputStream, String filename, String bizPath, String customBucket) {
        String file_url = "";
        if (inputStream == null || filename == null || filename.trim().isEmpty()) {
            return file_url;
        }
        try {
            bizPath = StrAttackFilter.filter(bizPath);
            if (bizPath != null && bizPath.startsWith("resource/")) {
                bizPath = bizPath.substring(9);
            }
            String newBucket = bucketName;
            if (oConvertUtils.isNotEmpty(customBucket)) {
                newBucket = customBucket;
            }
            initMinio();
            if (!minioClient.bucketExists(newBucket)) {
                minioClient.makeBucket(newBucket);
                setPolicy(bucketName);
                log.info("create a new bucket." + bucketName);
            }
            String orgName = CommonUtils.getFileName(filename);
            if (orgName == null || orgName.lastIndexOf('.') <= 0) {
                return file_url;
            }
            String objectName = bizPath + "/" + orgName.substring(0, orgName.lastIndexOf('.')) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf('.'));
            if (objectName.startsWith("/")) {
                objectName = objectName.substring(1);
            }
            int available = inputStream.available();
            minioClient.putObject(newBucket, objectName, inputStream, available, "application/octet-stream");
            inputStream.close();
            file_url = getWholeMinioUrl() + newBucket + "/" + objectName;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new IllegalArgumentException(e);
        }
        return file_url;
    }

    /**
     * 文件上传
     *
     * @param file
     * @param bizPath
     * @return
     */
    //这一个只能是common工具类中调用，其他的类想上传文件，必须用common工具类中的上传，不能直接用这个
    //需要改为非public
    static String upload(MultipartFile file, String bizPath) {
        return  upload(file, bizPath, null);
    }

    /**
     * 获取文件流
     *
     * @param bucketName
     * @param objectName
     * @return
     */
    public static InputStream getMinioFile(String bucketName, String objectName) {
        InputStream inputStream = null;
        try {
            if (bucketName == null || bucketName.trim().isEmpty() || objectName == null || objectName.trim().isEmpty()) {
                return null;
            }
            initMinio();
            inputStream = minioClient.getObject(bucketName, objectName);
        } catch (Exception e) {
            log.info("文件获取失败" + e.getMessage());
        }
        return inputStream;
    }

//    /**
//     * 删除文件
//     * @param bucketName
//     * @param objectName
//     * @throws Exception
//     */
//    public static void removeObject(String bucketName, String objectName) {
//        try {
//            initMinio();
//            minioClient.removeObject(bucketName, objectName);
//        }catch (Exception e){
//            log.info("文件删除失败" + e.getMessage());
//        }
//    }

    /**
     * 获取文件信息
     *
     * @param minioFileRelativePath
     * @throws Exception
     */
    public static ObjectStat getObjectInfo(String minioFileRelativePath) throws InvalidArgumentException, InvalidBucketNameException, InsufficientDataException, XmlPullParserException, ErrorResponseException, NoSuchAlgorithmException, IOException, NoResponseException, InvalidKeyException, InvalidResponseException, InternalException {
        initMinio();
        String[] bucketNameAndObjectName = getBucketNameAndObjectNameFromRelativePath(minioFileRelativePath);
        String bucketName = bucketNameAndObjectName[0];
        String objectName = bucketNameAndObjectName[1];
        return minioClient.statObject(bucketName, objectName);
    }


    public static InputStream getInputstreamByRelativePath(String minioFileRelativePath) {
        try {
            if (minioFileRelativePath == null || minioFileRelativePath.trim().isEmpty()) {
                return null;
            }
            initMinio();
            String[] bucketNameAndObjectName = getBucketNameAndObjectNameFromRelativePath(minioFileRelativePath);
            if (bucketNameAndObjectName == null
                    || bucketNameAndObjectName.length < 2
                    || bucketNameAndObjectName[0] == null || bucketNameAndObjectName[0].trim().isEmpty()
                    || bucketNameAndObjectName[1] == null || bucketNameAndObjectName[1].trim().isEmpty()) {
                return null;
            }
            String bucketName = bucketNameAndObjectName[0];
            String objectName = bucketNameAndObjectName[1];
            InputStream inputStream = minioClient.getObject(bucketName, objectName);
            return inputStream;
        } catch (Exception e) {
            log.error("获取文件失败！", e);
        }
        return null;
    }

    public static InputStream getInputstreamByMinioURL(String minioURL) {
        try {
            if (minioURL == null || minioURL.trim().isEmpty()) {
                return null;
            }
            initMinio();
            String[] bucketNameAndObjectName = getBucketNameAndObjectNameFromUrl(minioURL);
            if (bucketNameAndObjectName == null
                    || bucketNameAndObjectName.length < 2
                    || bucketNameAndObjectName[0] == null || bucketNameAndObjectName[0].trim().isEmpty()
                    || bucketNameAndObjectName[1] == null || bucketNameAndObjectName[1].trim().isEmpty()) {
                return null;
            }
            String bucketName = bucketNameAndObjectName[0];
            String objectName = bucketNameAndObjectName[1];
            InputStream inputStream = minioClient.getObject(bucketName, objectName);
            return inputStream;
        } catch (Exception e) {
            log.error("获取文件失败" + e.getMessage());
        }
        return null;
    }

    public static void removeObjectByRelativePath(String minioFileRelativePath) {
        try {
            if (minioFileRelativePath == null || minioFileRelativePath.trim().isEmpty()) {
                return;
            }
            initMinio();
            String[] bucketNameAndObjectName = getBucketNameAndObjectNameFromRelativePath(minioFileRelativePath);
            if (bucketNameAndObjectName == null
                    || bucketNameAndObjectName.length < 2
                    || bucketNameAndObjectName[0] == null || bucketNameAndObjectName[0].trim().isEmpty()
                    || bucketNameAndObjectName[1] == null || bucketNameAndObjectName[1].trim().isEmpty()) {
                return;
            }
            String bucketName = bucketNameAndObjectName[0];
            String objectName = bucketNameAndObjectName[1];
            minioClient.removeObject(bucketName, objectName);
        } catch (Exception e) {
            log.error("删除文件失败" + e.getMessage());
        }
    }

    static String[] getBucketNameAndObjectNameFromRelativePath(String minioFileUrl) {
        if (minioFileUrl.startsWith("/")) {
            minioFileUrl = minioFileUrl.substring(1);
        }
        int firstSlash = minioFileUrl.indexOf('/');
        String bucketName = minioFileUrl.substring(0, firstSlash);
        String objectName = minioFileUrl.substring(firstSlash + 1);
        return new String[]{bucketName, objectName};
    }

    public static void removeObject(String minioFileUrl) {
        try {
            if (minioFileUrl == null || minioFileUrl.trim().isEmpty()) {
                return;
            }
            initMinio();
            String[] bucketNameAndObjectName = getBucketNameAndObjectNameFromUrl(minioFileUrl);
            if (bucketNameAndObjectName == null
                    || bucketNameAndObjectName.length < 2
                    || bucketNameAndObjectName[0] == null || bucketNameAndObjectName[0].trim().isEmpty()
                    || bucketNameAndObjectName[1] == null || bucketNameAndObjectName[1].trim().isEmpty()) {
                return;
            }
            String bucketName = bucketNameAndObjectName[0];
            String objectName = bucketNameAndObjectName[1];
            minioClient.removeObject(bucketName, objectName);
        } catch (Exception e) {
            log.error("文件删除失败" + e.getMessage());
        }
    }

    //传参为http://开头或者https://开头的完整的url
    public static InputStream getObjectByURL(String minioFileUrl) {
        try {
            if (minioFileUrl == null || minioFileUrl.trim().isEmpty()) {
                return null;
            }
            initMinio();
            String[] bucketNameAndObjectName = getBucketNameAndObjectNameFromUrl(minioFileUrl);
            if (bucketNameAndObjectName == null
                    || bucketNameAndObjectName.length < 2
                    || bucketNameAndObjectName[0] == null || bucketNameAndObjectName[0].trim().isEmpty()
                    || bucketNameAndObjectName[1] == null || bucketNameAndObjectName[1].trim().isEmpty()) {
                return null;
            }
            String bucketName = bucketNameAndObjectName[0];
            String objectName = bucketNameAndObjectName[1];
            InputStream inputStream = minioClient.getObject(bucketName, objectName);
            return inputStream;
        } catch (Exception e) {
            log.error("获取文件失败" + e.getMessage());
        }
        return null;
    }

    static String[] getBucketNameAndObjectNameFromUrl(String minioFileUrl) {
        if (minioFileUrl.startsWith("http")) {
            int firstSlash = minioFileUrl.indexOf("/", 8);
            int secondSlash = minioFileUrl.indexOf("/", firstSlash + 1);
            String bucketName = minioFileUrl.substring(firstSlash + 1, secondSlash);
            String objectName = minioFileUrl.substring(secondSlash + 1);
            String[] strs = new String[]{bucketName, objectName};
            return strs;
        }
        if (minioFileUrl.startsWith(FileStorageTypePrefix.MINIO_PREFIX)) {
            minioFileUrl = minioFileUrl.replaceFirst(FileStorageTypePrefix.MINIO_PREFIX, "");
        }
        int firstSlash = minioFileUrl.indexOf('/');
        String bucketName = minioFileUrl.substring(0, firstSlash);
        String objectName = minioFileUrl.substring(firstSlash + 1);
        String[] strs = new String[]{bucketName, objectName};
        return strs;
    }

    public static void ListFilesInFolder(String bucketName, String folderName) {
        try {
            if (bucketName == null || bucketName.trim().isEmpty() || folderName == null || folderName.trim().isEmpty()) {
                return;
            }
            initMinio();
            Iterable<Result<Item>> listObjects = minioClient.listObjects(bucketName, folderName);
            if (listObjects == null) {
                return;
            }
            for (Result<Item> result : listObjects) {
                Item item = result.get();
                if (item != null && item.objectName() != null) {
                    String objectName = item.objectName();
                    int folderLen = folderName.length() + 1;
                    if (objectName.length() > folderLen) {
                        System.out.println(objectName.substring(folderLen));
                    }
                }
            }
        } catch (Exception e) {
            log.error("获取文件列表失败！" + e.getMessage());
        }
    }

//    public static InputStream getObjectByBucketNameAndObjectName(String bucketName,String objectName) {
//        try {
//            initMinio();
//            InputStream inputStream = minioClient.getObject(bucketName,objectName);
//            return inputStream;
//        }catch (Exception e){
//            log.info("获取minio文件失败" + e.getMessage());
//        }
//        return null;
//    }

    /**
     * 获取文件外链
     *
     * @param bucketName
     * @param objectName
     * @param expires
     * @return
     */
    public static String getObjectURL(String bucketName, String objectName, Integer expires) {
        try {
            if (bucketName == null || bucketName.trim().isEmpty() || objectName == null || objectName.trim().isEmpty()) {
                return null;
            }
            initMinio();
            String url = minioClient.presignedGetObject(bucketName, objectName, expires != null ? expires : 60 * 60 * 24);
            if (url == null) {
                return null;
            }
            return URLDecoder.decode(url, "UTF-8");
        } catch (Exception e) {
            log.error("文件路径获取失败" + e.getMessage());
        }
        return null;
    }

    /**
     * 初始化客户端
     * @return
     */
    public static MinioClient initMinio() {
        if (minioClient == null) {
            try {
                if (secure) {
                    minioClient = new MinioClient(minioUrl, port, minioName, minioPass, null, true, getUnsafeOkHttpClient());
                } else {
                    minioClient = new MinioClient(getIntranetMinioUrl(), minioName, minioPass);
                }
            } catch (InvalidEndpointException e) {
                e.printStackTrace();
            } catch (InvalidPortException e) {
                e.printStackTrace();
            }
        }
        return minioClient;
    }


//    public MinioClient minioClient(MinioProperties properties) throws InvalidPortException, InvalidEndpointException {
//        return new MinioClient(properties.getUrl(), 9000, properties.getName(), properties.getPassword(), null, true, getUnsafeOkHttpClient());
//    }

    public static SSLSocketFactory getSSLSocketFactory() {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            TrustManager[] trustManagers = getTrustManager();
            if (trustManagers == null) {
                throw new IllegalStateException("TrustManager 初始化失败");
            }
            sslContext.init(null, trustManagers, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("SSL算法不支持", e);
        } catch (KeyManagementException e) {
            throw new IllegalStateException("SSL上下文初始化失败", e);
        }
    }

    private static TrustManager[] getTrustManager() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }

    public static HostnameVerifier getHostnameVerifier() {
        return (s, sslSession) -> true;
    }

    public static X509TrustManager getX509TrustManager() {
        X509TrustManager trustManager = null;
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers == null || trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:" + (trustManagers != null ? Arrays.toString(trustManagers) : "null"));
            }
            trustManager = (X509TrustManager) trustManagers[0];
        } catch (Exception e) {
            e.printStackTrace();
        }
        return trustManager;
    }


    public static OkHttpClient getUnsafeOkHttpClient() {
        return new OkHttpClient.Builder()
                .readTimeout(60, TimeUnit.SECONDS)
                .connectTimeout(60, TimeUnit.SECONDS)
//            .sslSocketFactory(SSLSocketClient.getSSLSocketFactory())
                .sslSocketFactory(getSSLSocketFactory(), getX509TrustManager())
                .hostnameVerifier(getHostnameVerifier())
                .build();
    }

    static String getWholeMinioUrl() {
        if (StringUtils.isNotBlank(externalNetworkUrl)) {
            return externalNetworkUrl;
        }
        return minioUrl + ":" + port + "/";
    }

    static String getIntranetMinioUrl() {
        return minioUrl + ":" + port + "/";
    }


    //这是简化版的上传文件，暂时不用了
//    /**
//     * 上传文件到minio
//     * @param stream
//     * @param relativePath
//     * @return
//     */
//    public static String upload(InputStream stream,String relativePath) throws Exception {
//        initMinio();
//        bucketInit();
//        minioClient.putObject(bucketName, relativePath, stream, stream.available(), "application/octet-stream");
//        stream.close();
//        return getWholeMinioUrl() + bucketName + "/" + relativePath;
//    }

    public static void bucketInit() throws InvalidBucketNameException, InsufficientDataException, XmlPullParserException, ErrorResponseException, NoSuchAlgorithmException, IOException, NoResponseException, InvalidKeyException, InvalidResponseException, InternalException, RegionConflictException {
        if (!minioClient.bucketExists(bucketName)) {
            // 创建一个名为ota的存储桶
            minioClient.makeBucket(bucketName);
            setPolicy(bucketName);
            log.info("create a new bucket." + bucketName);
        }
    }

    public static String copyFileFromFlowableToKnowledge(String minioUrl)
            throws RegionConflictException, InvalidBucketNameException, InsufficientDataException, XmlPullParserException, ErrorResponseException, NoSuchAlgorithmException, IOException, NoResponseException, InvalidKeyException, InvalidResponseException, InternalException, InvalidArgumentException {
        initMinio();
        bucketInit();
        String[] bucketNameAndObjectName = getBucketNameAndObjectNameFromUrl(minioUrl);
        String sourceBucketName = bucketNameAndObjectName[0];
        String sourceObjectName = bucketNameAndObjectName[1];
        int i = sourceObjectName.lastIndexOf('.');
        String targetObjectName = sourceObjectName.substring(0, i) + "(2)" + sourceObjectName.substring(i);
        minioClient.copyObject(bucketName, targetObjectName, null, null, sourceBucketName, sourceObjectName, null, new CopyConditions());
        return FileStorageTypePrefix.MINIO_PREFIX + bucketName + "/" + targetObjectName;
    }

    public static boolean checkFileExists(String objectName) {
        return checkFileExists(bucketName, objectName);
    }

    public static boolean checkFileExists(String[] bucketNameAndObjectName) {
        return checkFileExists(bucketNameAndObjectName[0], bucketNameAndObjectName[1]);
    }

    public static boolean checkFileExists(String objectName) {
        try {
            if (objectName == null || objectName.trim().isEmpty()) {
                return false;
            }
            minioClient.statObject(bucketName, objectName);
            return true;
        } catch (Exception ignored) {
        }
        return false;
    }

    static void setPolicy(String bucketName) {
        // Define the policy string
        String policy = "{\n" +
                "    \"Version\": \"2012-10-17\",\n" +
                "    \"Statement\": [\n" +
                "        {\n" +
                "            \"Action\": [\n" +
                "                \"s3:GetObject\"\n" +
                "            ],\n" +
                "            \"Effect\": \"Allow\",\n" +
                "            \"Principal\": {\n" +
                "                \"AWS\": [\n" +
                "                    \"*\"\n" +
                "                ]\n" +
                "            },\n" +
                "            \"Resource\": [\n" +
                "                \"arn:aws:s3:::" + bucketName + "/*\"\n" +
                "            ]\n" +
                "        }\n" +
                "    ]\n" +
                "}";

        // Set the bucket policy
        try {
            if (bucketName == null || bucketName.trim().isEmpty() || policy == null || policy.trim().isEmpty()) {
                return;
            }
            minioClient.setBucketPolicy(bucketName, policy);
        } catch (Exception e) {
            throw new IllegalStateException("设置MinIO Bucket策略失败: " + bucketName, e);
        }
    }
}
